import re
import importlib
import logging
import json
import os
from dataclasses import dataclass, field, asdict


from filelock import FileLock
from starlette.staticfiles import StaticFiles
from typing import List

PLUGIN_DIR = 'plugin'
PLUGIN_STATE_FILE = 'plugin_states.json'
PLUGIN_STATE_FILE_PATH = f'{PLUGIN_DIR}/{PLUGIN_STATE_FILE}'
import cachetools
#全局缓存
#缓存用于保存插件管理器菜单
cache_plugin_menu = cachetools.Cache(maxsize=1)

#用于减少PLUGIN_STATE_FILE文件读取的信息和上次更改时间缓存
#键值
#change_time：插件状态更改时间
#info:关于读取并保存插件状态文件的插件信息
cache_plugin_state = cachetools.Cache(maxsize=2)


# 保存已激活插件的模块信息


@dataclass
class PluginState:
    plugin_name: str  # 经理信息显示名称
    module_name: str  # 插件的模块名称
    is_enable: field(default=False)  # bool on/off 状态


def get_all_plugin_admin_menu_id_name():
    extracted_tuples = []
    plugin_list = get_admin_plugin_menus()
    for plugin_dict in plugin_list:
        # 每个插件菜单
        for plugin_items in plugin_dict.values():
            for item in plugin_items:

                if 'id' in item and 'name' in item:
                    extracted_tuples.append((item['id'], item['name']))
    return extracted_tuples


def get_admin_menu_id_by_path(current_path: str):
    plugin_list = get_admin_plugin_menus()
 
    for plugin_dict in plugin_list:
        for plugin_items in plugin_dict.values():
            for item in plugin_items:

                if 'url' in item and 'id' in item:
                    if item.get('url') == current_path:
                        return item.get('id')
    return None


def get_all_plugin_info(plugin_dir=PLUGIN_DIR):
    """PLUGIN_DIR 获取文件夹信息内的所有包. (包括禁用的插件)
    Args:
        plugin_dir (str): Plg-in文件夹
    Returns:
        all_plugin_info (list): 插件信息列表
    """
    plugin_list = []
    for module_name in os.listdir(plugin_dir):
        module_path = os.path.join(plugin_dir, module_name)
        if module_name == '__pycache__':
            continue
        if os.path.isdir(module_path):
            info = get_plugin_info(module_name, plugin_dir)
            info['module_name'] = module_name
            plugin_list.append(info)

    plugin_state = read_plugin_state()
    all_plugin_info = []
    for plugin in plugin_list:
        for state in plugin_state:
            if plugin['module_name'] == state.module_name:
                plugin['is_enable'] = state.is_enable
                break
            else:
                plugin['is_enable'] = False
        all_plugin_info.append(plugin)
    return all_plugin_info


def get_plugin_state_cache():
    """获取当前插件状态文件的缓存.
    Returns:
        active_plugin_info (dict): 激活的插件信息
    """
    return cache_plugin_state.__getitem__('info') or {}


def get_all_plugin_module_names(plugin_dir=PLUGIN_DIR):
    """获取插件文件夹中所有插件的名称. (包括残疾人)
    Args:
        plugin_dir (str): Plg-in文件夹
    Returns:
        list: plugin_name_list 插件名称列表
    """
    plugin_names = []
    for module_name in os.listdir(plugin_dir):
        module_path = os.path.join(plugin_dir, module_name)
        if module_name == '__pycache__':
            continue
        if os.path.isdir(module_path):
            plugin_names.append(module_name)
    return plugin_names


def get_plugin_state_change_time():
    """返回插件状态更改时间.
    Returns:
        float: mtime 插件状态更改时间
    """
    if not os.path.isfile(PLUGIN_STATE_FILE_PATH):
        return 0

    return os.path.getmtime(PLUGIN_STATE_FILE_PATH)


def get_plugin_info(module_name, plugin_dir=PLUGIN_DIR):
    """返回插件信息.
    Args:
        module_name (str): 插件模块名称 - 单个包文件夹名称
        plugin_dir (str): 插件根文件夹
    Returns:
        dict: info 插件信息
    """
    info = {}
    path = os.path.join(plugin_dir, module_name)

    if os.path.isdir(path):
        screenshot_url = ''

        if os.path.exists(os.path.join(path, 'screenshot.webp')):
            screenshot_url = f"/admin/plugin/screenshot/{module_name}"

        elif os.path.exists(os.path.join(path, 'screenshot.png')):
            screenshot_url = f"/admin/plugin/screenshot/{module_name}"

        info['screenshot'] = screenshot_url
        info['module_name'] = module_name

        text = os.path.join(path, 'readme.txt')
        if not os.path.isfile(text):
            return info

        with open(text, 'r', encoding="UTF-8") as f:
            content = [line.strip() for line in f.readlines()]

        patterns = [
            ("^Plugin Name:(.+)$", "plugin_name"),
            ("^Plugin URI:(.+)$", "plugin_uri"),
            ("^Maker:(.+)$", "maker"),
            ("^Maker URI:(.+)$", "maker_uri"),
            ("^Version:(.+)$", "version"),
            ("^Detail:(.+)$", "detail"),
            ("^License:(.+)$", "license"),
            ("^License URI:(.+)$", "license_uri")
        ]

        for line in content:
            for pattern, key in patterns:

                match = re.search(pattern, line, re.I)
                if match:
                    info[key] = match.group(1).strip()

    return info


def get_admin_plugin_menus():
    """返回存储在全局缓存中的管理器菜单.
    Returns:
        list: 管理器菜单列表
    """
    # 区域变量 cache_plugin_menu 从带来
    return cache_plugin_menu.get('admin_menus')


def delete_router_by_tagname(app, tagname: str):
    """删除具有标记名称的已注册路由器
    Args:
        app (FastAPI): FastAPI 对象
        tagname (str): 标签名称
    """
    filtered_routes = [route_obj for route_obj in app.routes if
                       not (hasattr(route_obj, "tags") and tagname in route_obj.tags)]

    app.router.routes = filtered_routes


def read_plugin_state() -> List[PluginState]:
# “从plugin_states.json读取插件的活动状态。
# Returns:
# 返回PluginState列表
# Examples:
# 更改插件状态值时读取。
# # """
    if not os.path.isfile(PLUGIN_STATE_FILE_PATH):
        return []

    lock = FileLock(f"{PLUGIN_DIR}/{PLUGIN_STATE_FILE}.lock", timeout=5)
    plugin_state = []
    with lock:
        with open(PLUGIN_STATE_FILE_PATH, 'r', encoding="UTF-8") as file:
            # 如果你提前读了，比如检查文件的内容，数据就会消失。.
            # json.load() 你必须叫它正确。.
            try:
                plugin_state = json.load(file)
            except Exception as e:
                # plugin_states.json 该文件是 json 如果不适合格式，则无法加载插件.
                # 空文件出现错误. 错误消息: 'Expecting value: line 1 column 1 (char 0)'
                # json 或格式化它 plugin_states.json 您必须在管理员中设置文件和新的插件.
                logging.critical("/plugin/plugin_states.json json validate error. not load any plugin!.")
                logging.critical("It's not allow empty file. check json format. "
                                 f"or remove {PLUGIN_STATE_FILE} file.")
                logging.critical(e)

    plugin_state_list = []

    for plugin in plugin_state:
        state = PluginState(**plugin)
        plugin_state_list.append(state)
    return plugin_state_list


def write_plugin_state(plugin_states: List[PluginState]):
    """插件活动状态 plugin_states.json 记录在.
    Args:
        plugin_states (list): 插件列表
    Raises:
        Timeout: 文件 lock 在 Timeout 当它发生时
    Examples:
        初始安装, 仅在更改管理员菜单中的插件状态值时使用
    """
    if not os.path.exists(PLUGIN_STATE_FILE_PATH):
        with open(PLUGIN_STATE_FILE_PATH, 'w', encoding="UTF-8") as file:
            json.dump({}, file, indent=4, ensure_ascii=False)

    if not plugin_states:  # [] 空白列表
        return
    plugin_states_dict = [asdict(plugin) for plugin in plugin_states]

    lock = FileLock(f"{PLUGIN_DIR}/{PLUGIN_STATE_FILE}.lock", timeout=5)
    with lock:
        with open(PLUGIN_STATE_FILE_PATH, 'w', encoding="UTF-8") as file:
            json.dump(plugin_states_dict, file, indent=4, ensure_ascii=False)


def import_plugin_by_states(plugin_states: List[PluginState], plugin_dir=PLUGIN_DIR) -> List[PluginState]:
# “根据插件状态值导入插件。
# Args:
# plugin_states（list）：插件状态列表
# plugin_dir（str）：插件文件夹
# Returns:
# List[PluginState]：插件列表
# Examples:
# main，启动单板（启动进程）

    plugin_list = []
    for plugin in plugin_states:
        if plugin.is_enable:
            full_module_name = f"{plugin_dir}.{plugin.module_name}"
            importlib.import_module(full_module_name)
            plugin_list.append(plugin)
    return plugin_list


def register_plugin_admin_menu(plugin_states, plugin_dir=PLUGIN_DIR):
    """注册插件的管理器菜单.
    Args:
        plugin_states (list): 插件状态列表
        plugin_dir (str): Plg-in文件夹
    Returns:
        list: admin_menus 管理器菜单列表
        list: admin_menus 관리자 메뉴 목록
    """
    admin_menus = []
    for plugin in plugin_states:
        if plugin.is_enable:
            admin_module_name = f"{plugin_dir}.{plugin.module_name}.admin"
            module = importlib.import_module(admin_module_name)
            if module:
                get_menu_function = getattr(module, 'register_admin_menu', None)
                if get_menu_function:
                    admin_menus.append(get_menu_function())
    return admin_menus


def register_plugin(plugin_states, plugin_dir=PLUGIN_DIR):
    """注册插件的管理器菜单.
    Args:
        plugin_states (list): 插件状态列表
        plugin_dir (str): Plg-in文件夹
    Returns:
        list: admin_menus 管理器菜单列表
    """
    for plugin in plugin_states:
        if plugin.is_enable:
            module_name = f"{plugin_dir}.{plugin.module_name}"
            module = importlib.import_module(module_name)
            if module:
                register_function = getattr(module, 'register_plugin', None)
                if register_function:
                    register_function()
                    logging.info(f"register_plugin: {module_name}")


def unregister_plugin(plugin_states, plugin_dir=PLUGIN_DIR):
    """拔掉已注册的插件. unregister_plugin() 运行.
    Args:
        plugin_states (list): 插件状态列表
        plugin_dir (str): Plg-in文件夹
    Returns:
        list: admin_menus 管理器菜单列表
    """
    for plugin in plugin_states:
        if not plugin.is_enable:
            module_name = f"{plugin_dir}.{plugin.module_name}"
            module = importlib.import_module(module_name)
            if module:
                unregister_function = getattr(module, 'unregister_plugin', None)
                if unregister_function:
                    unregister_function()
                    logging.info(f"unregister_plugin: {module_name}")


def register_statics(app, plugin_info: List[PluginState], plugin_dir=PLUGIN_DIR):
    """插件的 static 注册.
    Args:
        app: FastAPI()
        plugin_info: 要注册的插件信息
        plugin_dir: Plg-in文件夹
    """
    for plugin in plugin_info:
        try:
            app.mount(
                f"/plugin/{plugin.module_name}/static",
                StaticFiles(directory=f"{plugin_dir}/{plugin.module_name}/static"),
                name=f"{plugin.module_name}"
            )
        except Exception as e:
            logging.warning(f"register_statics: {e}")
